module utils.color;

import std.string;

enum ColorMappingMode {
    none,
    rgbCut,
    rgbNonlinear,
    lumaNonlinear
}

//--- 7 digits approximation of precise values
static const float rgb2xyzD65Mat[3][3] =
[ [ 0.412424, 0.357579, 0.180464 ],
  [ 0.212656, 0.715158, 0.072186 ],
  [ 0.019332, 0.119193, 0.950444 ] ];

static const float xyz2rgbD65Mat[3][3] =
[ [  3.240708, -1.537259, -0.498570 ],
  [ -0.969257,  1.875995,  0.041555 ],
  [  0.055636, -0.203996,  1.057069 ] ];

public void transform(float[3][3] mat,
    float i1, float i2, float i3, out float o1, out float o2, out float o3)
{
    o1 = mat[0][0]*i1 + mat[0][1]*i2 + mat[0][2]*i3;
    o2 = mat[1][0]*i1 + mat[1][1]*i2 + mat[1][2]*i3;
    o3 = mat[2][0]*i1 + mat[2][1]*i2 + mat[2][2]*i3;
}

struct RGBColor {
    float r = 0.0f;
    float g = 0.0f;
    float b = 0.0f;

    public char[] toString() {
        return "("~std.string.toString(r)~","~std.string.toString(g)~","~std.string.toString(b)~")";
    }

    public static RGBColor opCall(float r, float g, float b) {
        RGBColor ret;
        ret.r = r;
        ret.g = g;
        ret.b = b;
        return ret;
    }
    public static RGBColor opCall(float c) {
        RGBColor ret;
        ret.r = c;
        ret.g = c;
        ret.b = c;
        return ret;
    }

    public YUVColor toYUV() {
        YUVColor ret;
        ret.y = 0.299*r + 0.587*g + 0.114*b;
        ret.u = -0.1471376975169300226*r - 0.2888623024830699774*g + 0.436*b;
        ret.v = 0.615*r - 0.5149857346647646220*g - 0.1000142653352353780*b;
        return ret;
    }

    public ubyte rByte() {
        return cast(ubyte)(r*255.0f);
    }

    public ubyte gByte() {
        return cast(ubyte)(g*255.0f);
    }

    public ubyte bByte() {
        return cast(ubyte)(b*255.0f);
    }

    public RGBColor opAdd(RGBColor c) {
        return RGBColor(r+c.r, g+c.g, b+c.b);
    }
    public void opAddAssign(RGBColor c) {
        r += c.r;
        g += c.g;
        b += c.b;
    }

    public RGBColor opSub(RGBColor c) {
        return RGBColor(r-c.r, g-c.g, b-c.b);
    }
    public void opSubAssign(RGBColor c) {
        r -= c.r;
        g -= c.g;
        b -= c.b;
    }

    public void opDivAssign(int scalar) {
        r /= scalar;
        g /= scalar;
        b /= scalar;
    }

    public RGBColor opMul(float scalar) {
        return RGBColor(r*scalar, g*scalar, b*scalar);
    }
    public RGBColor opMul_r(float scalar) {
        return opMul(scalar);
    }

    public void opMulAssign(float scalar) {
        r *= scalar;
        g *= scalar;
        b *= scalar;
    }

    //entry-wise multiplication
    public RGBColor opMul(RGBColor col) {
        RGBColor n = *this;
        n.r *= col.r;
        n.g *= col.g;
        n.b *= col.b;
        return n;
    }

    public void limit0_1() {
        r = r>1.0f?1.0f:r;
        g = g>1.0f?1.0f:g;
        b = b>1.0f?1.0f:b;
        r = r<0?0:r;
        g = g<0?0:g;
        b = b<0?0:b;
    }

    public void map(ColorMappingMode m) {
        switch (m) {
            case ColorMappingMode.rgbCut:
                r = r>1.0f?1.0f:r;
                g = g>1.0f?1.0f:g;
                b = b>1.0f?1.0f:b;
                break;
            case ColorMappingMode.rgbNonlinear:
                YUVColor yuv = toYUV();
                r = r/(0.5+r);
                g = g/(0.5+g);
                b = b/(0.5+b);
                break;
            case ColorMappingMode.lumaNonlinear:
                YUVColor yuv = toYUV();
                r = r/(0.7+0.7*yuv.y);
                g = g/(0.7+0.7*yuv.y);
                b = b/(0.7+0.7*yuv.y);
                limit0_1();
                break;
            default:
                break;
        }
    }
}

struct YUVColor {
    float y;
    float u;
    float v;

    public static YUVColor opCall(float y, float u, float v) {
        YUVColor ret;
        ret.y = y;
        ret.u = u;
        ret.v = v;
        return ret;
    }

    public RGBColor toRGB() {
        RGBColor ret;
        ret.r = y + 1.139837398373983740*v;
        ret.g = y - 0.3946517043589703515*u - 0.5805986066674976801*v;
        ret.b = y + 2.032110091743119266*u;
        return ret;
    }
}
